using System;
using System.Linq;
using System.Collections.Generic;
using FoundationLib.Ifc.Logging.Configuration;
using System.Diagnostics.Contracts;


namespace FoundationLib.Ifc.Logging 
{
	/// <summary>
	/// This class manages instances to loggers.
	/// </summary>
	public static class LogManager 
	{
		/// <summary>
		/// A set of business loggers.
		/// </summary>
		private static Dictionary<Type, IBusinessLogger> businessLoggers = new Dictionary<Type,IBusinessLogger>();
		/// <summary>
		/// A set of technical loggers.
		/// </summary>
		private static Dictionary<Type, ITechnicalLogger> technicalLoggers = new Dictionary<Type,ITechnicalLogger>();
		/// <summary>
		/// The logger config section
		/// </summary>
		private static LoggerConfigSection configuration;

		private const string TECH_LOGGER_DISCR = "technical";
		private const string BUSI_LOGGER_DISCR = "business";


		/// <summary>
		/// Makes sure that the logger configuration has been loaded.
		/// </summary>
		/// <returns>True if the section has been loaded, false otherwises</returns>
		private static bool EnsureConfigLoaded()
		{
			if (configuration == null)
				configuration = LoggerConfigSection.FromConfiguration();

			return (configuration != null);
		}

		/// <summary>
		/// Resolves the type of the logger referred to by the given configuration element.
		/// </summary>
		/// <param name="element">The logger configuration element</param>
		/// <returns>The logger's system type</returns>
		private static Type ResolveLoggerType(LoggerConfigElement element)
		{
			Contract.Requires(element != null);
			Contract.Requires(!String.IsNullOrEmpty(element.LoggerType) && !String.IsNullOrWhiteSpace(element.Implementation));
			Type ifcType = null;

			if (element.LoggerType == TECH_LOGGER_DISCR)
				ifcType = typeof(ITechnicalLogger);
			else if (element.LoggerType == BUSI_LOGGER_DISCR)
				ifcType = typeof(IBusinessLogger);

			Type implType = Type.GetType(element.Implementation, false);

			Contract.Assert(ifcType != null && implType != null);

			if (!implType.GetInterfaces().Contains(ifcType))
				return null;
			else
				return implType;
		}

		/// <summary>
		/// Creates a logger instance of the given type.
		/// </summary>
		/// <param name="name">The logger name</param>
		/// <param name="type">The logger type</param>
		/// <returns>The logger instance or null on error</returns>
		private static object CreateLogger(string name, string type)
		{
			LoggerConfigElement element = configuration.Loggers.Where(x => x.Name == name).FirstOrDefault();
			if (element == null)
				return null;

			if (element.LoggerType != type)
				return null;

			Type implType = ResolveLoggerType(element);
			if (implType == null)
				return null;

			if (type == TECH_LOGGER_DISCR)
			{
				if (!technicalLoggers.ContainsKey(implType))
				{
					ITechnicalLogger logger = Activator.CreateInstance(implType) as ITechnicalLogger;
					logger.Configuration = element;
					technicalLoggers.Add(implType, logger);
				}
				return technicalLoggers[implType];
			}
			else if (type == BUSI_LOGGER_DISCR)
			{
				if (!businessLoggers.ContainsKey(implType))
					businessLoggers.Add(implType, Activator.CreateInstance(implType) as IBusinessLogger);
				return businessLoggers[implType];
			}

			return null;
		}

		/// <summary>
		/// Gets the business logger with the given name.
		/// </summary>
		/// <param name="name">The business logger name in the configuration</param>
		/// <returns>The business logger instance or null if no such logger exists</returns>
		public static IBusinessLogger GetBusinessLogger(string name)
		{
			Contract.Requires(!string.IsNullOrWhiteSpace(name));
			Contract.Assert(EnsureConfigLoaded());

			IBusinessLogger instance = CreateLogger(name, BUSI_LOGGER_DISCR) as IBusinessLogger;
			return instance;
		}

		/// <summary>
		/// Gets the technical logger with the given name.
		/// </summary>
		/// <param name="name">The technical logger name in the configuration</param>
		/// <returns>The technical logger instance or null if no such logger exists</returns>
		public static ITechnicalLogger GetTechnicalLogger(string name)
		{
			Contract.Requires(!string.IsNullOrWhiteSpace(name));
			Contract.Assert(EnsureConfigLoaded());

			ITechnicalLogger instance = CreateLogger(name, TECH_LOGGER_DISCR) as ITechnicalLogger;
			return instance;
		}

		/// <summary>
		/// Gets the default business logger if one is defined in the configuration.
		/// </summary>
		public static IBusinessLogger BusinessLogger
		{
			get
			{
				Contract.Assert(EnsureConfigLoaded());

				LoggerConfigElement element = configuration.Loggers.Where(x => x.LoggerType == BUSI_LOGGER_DISCR &&
					x.IsDefault).FirstOrDefault();
				if (element == null)
					return null;

				IBusinessLogger instance = CreateLogger(element.Name, element.LoggerType) as IBusinessLogger;
				return instance;
			}
		}

		/// <summary>
		/// Gets the default technical logger if one is defined in the configuration.
		/// </summary>
		public static ITechnicalLogger TechnicalLogger
		{
			get
			{
				Contract.Assert(EnsureConfigLoaded());

				LoggerConfigElement element = configuration.Loggers.Where(x => x.LoggerType == TECH_LOGGER_DISCR &&
					x.IsDefault).FirstOrDefault();
				if (element == null)
					return null;

				ITechnicalLogger instance = CreateLogger(element.Name, element.LoggerType) as ITechnicalLogger;
				return instance;
			}
		}

	}

}